# PMake improvement programme Makefile (authored on 2025-10-29).
# Captures backlog items for enhancing the PMake core and project task catalogues.

.PHONY: all summary todo done progress next prev mark mark.start mark.done mark.block mark.wait mark.optional help streams stream.summary stream.todo stream.done lint \
        list-proj next-proj mark-proj-done \
        pmake.summary pmake.todo pmake.done pmake.progress pmake.next pmake.streams pmake.lint pmake.mark pmake.mark.start pmake.mark.done pmake.mark.block pmake.mark.wait pmake.mark.optional pmake.list-proj pmake.next-proj pmake.mark-proj-done \
        flex.summary flex.todo flex.done flex.progress flex.next flex.streams flex.lint \
        gsi.summary gsi.todo gsi.done gsi.progress gsi.next gsi.streams gsi.lint \
        wrf.summary wrf.todo wrf.done wrf.progress wrf.next wrf.streams wrf.lint wrf.list-proj wrf.next-proj wrf.mark-proj-done wrf.mark wrf.mark.start wrf.mark.done wrf.mark.block wrf.mark.wait wrf.mark.optional \
        wrf-mcv.summary wrf-mcv.todo wrf-mcv.done wrf-mcv.progress wrf-mcv.next wrf-mcv.streams wrf-mcv.lint wrf-mcv.list-proj wrf-mcv.next-proj wrf-mcv.mark-proj-done wrf-mcv.mark wrf-mcv.mark.start wrf-mcv.mark.done wrf-mcv.mark.block wrf-mcv.mark.wait wrf-mcv.mark.optional \
        wrf-se.summary wrf-se.todo wrf-se.done wrf-se.progress wrf-se.next wrf-se.streams wrf-se.lint wrf-se.list-proj wrf-se.next-proj wrf-se.mark-proj-done wrf-se.mark wrf-se.mark.start wrf-se.mark.done wrf-se.mark.block wrf-se.mark.wait wrf-se.mark.optional

SUPPORTED_PROJECTS := pmake flexinvert gsi wrf wrf-mcv wrf-se all

SELF_PATH := $(lastword $(MAKEFILE_LIST))
SELF_DIR := $(dir $(SELF_PATH))
PROJECTS_DIR := $(abspath $(SELF_DIR)..)

PROJECT_ROOT := $(PROJECTS_DIR)/pmake
PROJECT_INCLUDE_DIR := $(PROJECT_ROOT)/includes
PROJECT_META_FILE := $(PROJECT_INCLUDE_DIR)/meta.mk
PROJECT_TASK_FILE := $(PROJECT_INCLUDE_DIR)/tasks.mk
PROJECT_QUEUE_FILE := $(PROJECT_INCLUDE_DIR)/projects.mk
PROJECT_STATE_FILE := $(PROJECT_INCLUDE_DIR)/projects_state.mk
PYTHON ?= python3
TASKS_CLI := $(abspath $(PROJECTS_DIR)/../scripts/tasks_cli.py)

$(if $(wildcard $(PROJECT_META_FILE)),,$(error Missing metadata file $(PROJECT_META_FILE)))
$(if $(wildcard $(PROJECT_TASK_FILE)),,$(error Missing task catalogue $(PROJECT_TASK_FILE)))
$(if $(wildcard $(PROJECT_QUEUE_FILE)),,$(error Missing project queue file $(PROJECT_QUEUE_FILE)))

PROJECT_NAME_all := All Projects
PROJECT_NAME_pmake := PMake Core
PROJECT_NAME_flexinvert := FLEXINVERT
PROJECT_NAME_gsi := GSICoreAnalysis
PROJECT_NAME_wrf := WRF Rollup
PROJECT_NAME_wrf-mcv := WRF-MCV
PROJECT_NAME_wrf-se := WRF-SE

define PROJECT_DISPLAY_NAME
$(if $(PROJECT_NAME_$(1)),$(PROJECT_NAME_$(1)),$(1))
endef

include $(PROJECT_QUEUE_FILE)

ifeq ($(wildcard $(PROJECT_STATE_FILE)),)
$(shell printf 'PROJECT_DEFAULT := %s\nPROJECT_DONE :=\n' '$(firstword $(PROJECT_QUEUE))' > $(PROJECT_STATE_FILE))
endif

include $(PROJECT_STATE_FILE)

PROJECT_QUEUE_LIST := $(strip $(PROJECT_QUEUE))
PROJECT_DONE_LIST := $(strip $(PROJECT_DONE))
PROJECT_PENDING_LIST := $(filter-out $(PROJECT_DONE_LIST),$(PROJECT_QUEUE_LIST))
PROJECT_DEFAULT := $(if $(strip $(PROJECT_DEFAULT)),$(PROJECT_DEFAULT),$(if $(PROJECT_PENDING_LIST),$(firstword $(PROJECT_PENDING_LIST)),$(firstword $(PROJECT_QUEUE_LIST))))
PROJECT ?= $(PROJECT_DEFAULT)
ifeq ($(filter $(PROJECT),$(SUPPORTED_PROJECTS)),$(PROJECT))
else
$(error Unknown PROJECT '$(PROJECT)'. Supported projects: $(SUPPORTED_PROJECTS))
endif

define write_project_state
$(file >$(PROJECT_STATE_FILE),PROJECT_DEFAULT := $(1))
$(file >>$(PROJECT_STATE_FILE),PROJECT_DONE := $(strip $(2)))
endef

NEXT_ARGS := $(filter-out next,$(MAKECMDGOALS))
NEXT_INDEX_RAW := $(firstword $(NEXT_ARGS))
NEXT_INDEX := $(strip $(shell if [ -n "$(NEXT_INDEX_RAW)" ] && printf '%s' "$(NEXT_INDEX_RAW)" | grep -Eq '^[0-9]+$$'; then echo "$(NEXT_INDEX_RAW)"; else echo 1; fi))
NEXT_ARG_IS_NUM := $(strip $(shell if [ -n "$(NEXT_INDEX_RAW)" ] && printf '%s' "$(NEXT_INDEX_RAW)" | grep -Eq '^[0-9]+$$'; then echo 1; else echo 0; fi))

ifneq (,$(filter next,$(MAKECMDGOALS)))
ifeq ($(NEXT_ARG_IS_NUM),1)
.PHONY: $(NEXT_INDEX_RAW)
$(NEXT_INDEX_RAW):
	@:
endif
endif

PREV_ARGS := $(filter-out prev,$(MAKECMDGOALS))
PREV_INDEX_RAW := $(firstword $(PREV_ARGS))
PREV_INDEX := $(strip $(shell if [ -n "$(PREV_INDEX_RAW)" ] && printf '%s' "$(PREV_INDEX_RAW)" | grep -Eq '^[0-9]+$$'; then echo "$(PREV_INDEX_RAW)"; else echo 1; fi))
PREV_ARG_IS_NUM := $(strip $(shell if [ -n "$(PREV_INDEX_RAW)" ] && printf '%s' "$(PREV_INDEX_RAW)" | grep -Eq '^[0-9]+$$'; then echo 1; else echo 0; fi))

ifneq (,$(filter prev,$(MAKECMDGOALS)))
ifeq ($(PREV_ARG_IS_NUM),1)
.PHONY: $(PREV_INDEX_RAW)
$(PREV_INDEX_RAW):
	@:
endif
endif

# --- Task registration framework -------------------------------------------------

define REGISTER_TASK
$(eval TASK_IDS += $(1))
$(eval STATUS_$(1) := $(2))
$(eval CLASS_$(1) := $(3))
$(eval STREAM_$(1) := $(4))
$(eval TITLE_$(1) := $(5))
$(eval REF_$(1) := $(6))
endef

define REGISTER_META
$(eval OWNER_$(1) := $(2))
$(eval DUE_$(1) := $(3))
$(eval PRIO_$(1) := $(4))
$(eval TAGS_$(1) := $(5))
$(eval CRIT_$(1) := $(6))
$(eval EVID_$(1) := $(7))
endef

STATUS_EMOJI_done := [x]
STATUS_EMOJI_wip := [~]
STATUS_EMOJI_blocked := [!]
STATUS_EMOJI_waiting := [?]
STATUS_EMOJI_optional := ( )

status_symbol = $(or $(STATUS_EMOJI_$(1)),[ ])
class_badge = $(if $(filter optional,$(1)),[O],[E])
stream_label = $(or $(STREAM_LABEL_$(1)),$(1))

meta_pair = $(if $(strip $(2)),$(1)=$(2),)

define format_meta
$(strip $(call meta_pair,own,$(OWNER_$1)) $(call meta_pair,due,$(DUE_$1)) $(call meta_pair,p,$(PRIO_$1)) $(call meta_pair,tags,$(TAGS_$1)) $(call meta_pair,crit,$(CRIT_$1)) $(call meta_pair,evid,$(EVID_$1)))
endef

define append_meta
$(if $(strip $(call format_meta,$(1))), {$(call format_meta,$(1))},)
endef

define format_task
$(call status_symbol,$(STATUS_$(1))) $(call class_badge,$(CLASS_$(1))) $(1) - $(TITLE_$(1)) [$(REF_$(1))]$(call append_meta,$(1))
endef

define TASK_HAS_TAG
$(filter $(1),$(TAGS_$(2)))
endef

PROJECT_FILTER_TAG := $(if $(filter all,$(PROJECT)),,$(PROJECT))

define PROJECT_MATCH
$(if $(PROJECT_FILTER_TAG),$(if $(call TASK_HAS_TAG,$(PROJECT_FILTER_TAG),$(1)),$(1),),$(1))
endef

define PROJECT_TASKS
$(strip $(foreach t,$(TASK_IDS),$(if $(call TASK_HAS_TAG,$(1),$(t)),$(t),)))
endef

define ORDERED_PENDING_FOR
$(strip $(foreach t,$(ORDERED_PENDING_ALL),$(if $(call TASK_HAS_TAG,$(1),$(t)),$(if $(filter $(STATUS_$(t)),$(PENDING_STATUSES)),$(t),),)))
endef

define FALLBACK_PENDING_FOR
$(strip \
  $(foreach t,$(call PROJECT_TASKS,$(1)),$(if $(and $(filter $(STATUS_$(t)),$(PENDING_STATUSES)),$(filter essential,$(CLASS_$(t)))), $(t),)) \
  $(foreach t,$(call PROJECT_TASKS,$(1)),$(if $(and $(filter $(STATUS_$(t)),$(PENDING_STATUSES)),$(filter optional,$(CLASS_$(t)))), $(t),)) \
)
endef

define PENDING_QUEUE_FOR
$(strip $(or $(call ORDERED_PENDING_FOR,$(1)),$(call FALLBACK_PENDING_FOR,$(1))))
endef

define COMPLETED_QUEUE_FOR
$(strip $(or $(foreach t,$(ORDERED_DONE_ALL),$(if $(call TASK_HAS_TAG,$(1),$(t)),$(if $(filter done,$(STATUS_$(t))),$(t),),)),$(foreach t,$(call PROJECT_TASKS,$(1)),$(if $(filter done,$(STATUS_$(t))),$(t),))))
endef

pending_total_for = $(words $(call PENDING_QUEUE_FOR,$(1)))
next_task_for = $(word $(NEXT_INDEX),$(call PENDING_QUEUE_FOR,$(1)))
completed_queue_rev_for = $(strip $(call reverse_list,$(call COMPLETED_QUEUE_FOR,$(1))))
completed_total_for = $(words $(call completed_queue_rev_for,$(1)))
prev_task_for = $(word $(PREV_INDEX),$(call completed_queue_rev_for,$(1)))

FILTERED_TASKS = $(strip $(foreach t,$(TASK_IDS),$(if $(call PROJECT_MATCH,$(t)),$(t),)))

define tasks_in_statuses
$(strip $(foreach t,$(FILTERED_TASKS),$(if $(filter $(STATUS_$(t)),$(1)),$(t),)))
endef

status_count = $(words $(call tasks_in_statuses,$(1)))
stream_total = $(words $(call tasks_for_stream,$(1)))
stream_status_count = $(words $(call tasks_for_stream_status,$(1),$(2)))
stream_done = $(call stream_status_count,$(1),done)

define percent
$(shell awk 'BEGIN { total=$(2); done=$(1); if (total==0) { printf "0.0%%"; } else { printf "%.1f%%", (done*100.0)/total; } }')
endef

define reverse_list
$(if $(1),$(call reverse_list,$(wordlist 2,$(words $(1)),$(1))) $(firstword $(1)))
endef

OVERALL_TOTAL = $(words $(FILTERED_TASKS))
OVERALL_DONE = $(call status_count,done)
OVERALL_TODO = $(call status_count,todo)
OVERALL_WIP = $(call status_count,wip)
OVERALL_WAITING = $(call status_count,waiting)
OVERALL_BLOCKED = $(call status_count,blocked)
OVERALL_OPTIONAL = $(call status_count,optional)

define STREAM_PROGRESS_LINE
$(if $(filter-out 0,$(call stream_total,$(1))),$(info $(call stream_label,$(1)): done=$(call stream_done,$(1)) / total=$(call stream_total,$(1)) ($(call percent,$(call stream_done,$(1)),$(call stream_total,$(1)))) todo=$(call stream_status_count,$(1),todo) wip=$(call stream_status_count,$(1),wip) waiting=$(call stream_status_count,$(1),waiting) blocked=$(call stream_status_count,$(1),blocked) optional=$(call stream_status_count,$(1),optional)))
endef

tasks_for_stream = $(strip $(foreach t,$(FILTERED_TASKS),$(if $(filter $(1),$(STREAM_$(t))),$(t),)))
tasks_for_stream_status = $(strip $(foreach t,$(call tasks_for_stream,$(1)),$(if $(filter $(STATUS_$(t)),$(2)),$(t),)))

PENDING_STATUSES := todo wip waiting blocked optional
ORDERED_PENDING_ALL := $(strip $(shell $(PYTHON) "$(TASKS_CLI)" order --root "$(PROJECT_INCLUDE_DIR)"))
fallback_pending_essential = $(strip $(foreach t,$(FILTERED_TASKS),$(if $(and $(filter $(STATUS_$(t)),$(PENDING_STATUSES)),$(filter essential,$(CLASS_$(t)))),$(t),)))
fallback_pending_optional = $(strip $(foreach t,$(FILTERED_TASKS),$(if $(and $(filter $(STATUS_$(t)),$(PENDING_STATUSES)),$(filter optional,$(CLASS_$(t)))),$(t),)))
fallback_pending = $(strip $(fallback_pending_essential) $(fallback_pending_optional))
pending_queue = $(strip $(if $(ORDERED_PENDING_ALL),$(foreach t,$(ORDERED_PENDING_ALL),$(if $(call PROJECT_MATCH,$(t)),$(t),)),$(fallback_pending)))
pending_total = $(words $(pending_queue))
next_task = $(word $(NEXT_INDEX),$(pending_queue))
CURRENT_SEQUENCE_INDEX := $(NEXT_INDEX)
CURRENT_SEQUENCE_TOTAL := $(pending_total)

COMPLETED_STATUSES := done
ORDERED_DONE_ALL := $(strip $(shell $(PYTHON) "$(TASKS_CLI)" order --root "$(PROJECT_INCLUDE_DIR)" --statuses $(COMPLETED_STATUSES)))
fallback_done = $(strip $(foreach t,$(FILTERED_TASKS),$(if $(filter done,$(STATUS_$(t))),$(t),)))
completed_queue = $(strip $(if $(ORDERED_DONE_ALL),$(foreach t,$(ORDERED_DONE_ALL),$(if $(call PROJECT_MATCH,$(t)),$(t),)),$(fallback_done)))
completed_queue_rev = $(strip $(call reverse_list,$(completed_queue)))
completed_total = $(words $(completed_queue_rev))
prev_task = $(word $(PREV_INDEX),$(completed_queue_rev))
CURRENT_PREV_INDEX := $(PREV_INDEX)
CURRENT_PREV_TOTAL := $(completed_total)
task_dependencies = $(if $(strip $(DEPS_$(1))),$(DEPS_$(1)),none)

define PRINT_NEXT_PROMPT
$(info Implementation prompt:)
$(info   - Objective: $(TITLE_$(1)))
$(info   - Stream: $(call stream_label,$(STREAM_$(1))))
$(info   - Current status: $(STATUS_$(1)) ($(CLASS_$(1))))
$(info   - Dependencies: $(call task_dependencies,$(1)))
$(info   - Reference doc: $(REF_$(1)))
$(info   - Owner: $(if $(strip $(OWNER_$(1))),$(OWNER_$(1)),unassigned))
$(info   - Due: $(if $(strip $(DUE_$(1))),$(DUE_$(1)),n/a))
$(info   - Priority: $(if $(strip $(PRIO_$(1))),$(PRIO_$(1)),n/a))
$(info   - Tags: $(if $(strip $(TAGS_$(1))),$(TAGS_$(1)),n/a))
$(info   - Acceptance criteria: $(if $(strip $(CRIT_$(1))),$(CRIT_$(1)),n/a))
$(info   - Evidence: $(if $(strip $(EVID_$(1))),$(EVID_$(1)),n/a))
$(info   - Suggested actions:)
$(info     1. Review dependencies and confirm prerequisites are satisfied.)
$(info     2. Implement the required updates and document changes in relevant catalogues.)
$(info     3. Capture outcomes in the PMake worklog or mark the task with make mark.)
$(info   - Sequence coverage: $(CURRENT_SEQUENCE_INDEX) / $(CURRENT_SEQUENCE_TOTAL))
$(info )
endef

define PRINT_PREV_PROMPT
$(info Completion recap:)
$(info   - Objective: $(TITLE_$(1)))
$(info   - Stream: $(call stream_label,$(STREAM_$(1))))
$(info   - Current status: $(STATUS_$(1)) ($(CLASS_$(1))))
$(info   - Dependencies: $(call task_dependencies,$(1)))
$(info   - Reference doc: $(REF_$(1)))
$(info   - Owner: $(if $(strip $(OWNER_$(1))),$(OWNER_$(1)),unassigned))
$(info   - Tags: $(if $(strip $(TAGS_$(1))),$(TAGS_$(1)),n/a))
$(info   - Evidence: $(if $(strip $(EVID_$(1))),$(EVID_$(1)),n/a))
$(info   - Sequence coverage: $(CURRENT_PREV_INDEX) / $(CURRENT_PREV_TOTAL))
$(info )
endef

define PRINT_MULTI_NEXT
$(eval __proj := $(1))
$(eval __queue := $(call PENDING_QUEUE_FOR,$(__proj)))
$(eval __total := $(words $(__queue)))
$(if $(strip $(__queue)), \
    $(eval __task := $(word $(NEXT_INDEX),$(__queue))) \
    $(if $(strip $(__task)), \
        $(eval CURRENT_SEQUENCE_INDEX := $(NEXT_INDEX)) \
        $(eval CURRENT_SEQUENCE_TOTAL := $(__total)) \
        $(info [$(call PROJECT_DISPLAY_NAME,$(__proj))] Next task (position $(NEXT_INDEX) of $(__total)):) \
        $(info $(call format_task,$(__task))) \
        $(call PRINT_NEXT_PROMPT,$(__task)), \
        $(info [$(call PROJECT_DISPLAY_NAME,$(__proj))] Requested index $(NEXT_INDEX) exceeds pending task count $(__total). Use make "next $(__total)" PROJECT=$(__proj).) \
    ), \
    $(info [$(call PROJECT_DISPLAY_NAME,$(__proj))] All tasks are complete.) \
)
endef

define PRINT_MULTI_PREV
$(eval __proj := $(1))
$(eval __queue := $(call completed_queue_rev_for,$(__proj)))
$(eval __total := $(words $(__queue)))
$(if $(strip $(__queue)), \
    $(eval __task := $(word $(PREV_INDEX),$(__queue))) \
    $(if $(strip $(__task)), \
        $(eval CURRENT_PREV_INDEX := $(PREV_INDEX)) \
        $(eval CURRENT_PREV_TOTAL := $(__total)) \
        $(info [$(call PROJECT_DISPLAY_NAME,$(__proj))] Most recently completed task (position $(PREV_INDEX) of $(__total)):) \
        $(info $(call format_task,$(__task))) \
        $(call PRINT_PREV_PROMPT,$(__task)), \
        $(info [$(call PROJECT_DISPLAY_NAME,$(__proj))] Requested index $(PREV_INDEX) exceeds completed task count $(__total). Use make "prev $(__total)" PROJECT=$(__proj).) \
    ), \
    $(info [$(call PROJECT_DISPLAY_NAME,$(__proj))] No completed tasks found yet.) \
)
endef

define PRINT_SINGLE_NEXT_POSITIVE
$(eval CURRENT_SEQUENCE_INDEX := $(NEXT_INDEX))
$(eval CURRENT_SEQUENCE_TOTAL := $(pending_total))
$(info [$(call PROJECT_DISPLAY_NAME,$(PROJECT))] Next task to execute (position $(NEXT_INDEX) of $(pending_total)):)
$(info $(call format_task,$(next_task)))
$(call PRINT_NEXT_PROMPT,$(next_task))
endef

define PRINT_SINGLE_NEXT_NEGATIVE
$(if $(strip $(pending_queue)),
	$(info $(INDEX_OOB_MESSAGE))
	$(info $(INDEX_HINT_MESSAGE)),
	$(info $(NEXT_EMPTY_MESSAGE)))
endef

define PRINT_SINGLE_NEXT
$(if $(strip $(next_task)),$(call PRINT_SINGLE_NEXT_POSITIVE),$(call PRINT_SINGLE_NEXT_NEGATIVE))
endef

define PRINT_SINGLE_PREV_POSITIVE
$(eval CURRENT_PREV_INDEX := $(PREV_INDEX))
$(eval CURRENT_PREV_TOTAL := $(completed_total))
$(info [$(call PROJECT_DISPLAY_NAME,$(PROJECT))] Most recently completed task (position $(PREV_INDEX) of $(completed_total)):)
$(info $(call format_task,$(prev_task)))
$(call PRINT_PREV_PROMPT,$(prev_task))
endef

define PRINT_SINGLE_PREV_NEGATIVE
$(if $(strip $(completed_queue)),
	$(info $(PREV_OOB_MESSAGE))
	$(info $(PREV_HINT_MESSAGE)),
	$(info $(PREV_EMPTY_MESSAGE)))
endef

define PRINT_SINGLE_PREV
$(if $(strip $(prev_task)),$(call PRINT_SINGLE_PREV_POSITIVE),$(call PRINT_SINGLE_PREV_NEGATIVE))
endef

define PRINT_STREAM
$(if $(call tasks_for_stream,$(1)),$(info )$(info -- $(call stream_label,$(1)) --)$(foreach t,$(call tasks_for_stream,$(1)),$(info $(call format_task,$(t)))))
endef

define PRINT_STREAM_FILTER
$(if $(call tasks_for_stream_status,$(1),$(2)),$(info )$(info -- $(call stream_label,$(1)) --)$(foreach t,$(call tasks_for_stream_status,$(1),$(2)),$(info $(call format_task,$(t)))))
endef

DEP_TARGETS := 

define REGISTER_DEP
$(eval DEPS_$(1) := $(strip $(DEPS_$(1)) $(2)))
$(eval DEP_TARGETS := $(sort $(DEP_TARGETS) $(1)))
endef

define CHECK_TASK_DEPS
$(foreach dep,$(DEPS_$(1)),$(if $(and $(filter done,$(STATUS_$(1))),$(filter-out done,$(STATUS_$(dep)))),\
$(warning $(1) marked done but prerequisite $(dep) is $(STATUS_$(dep))),))
endef

PROJECT_CATALOG_DIR := $(PROJECT_ROOT)/
include $(PROJECT_META_FILE)
include $(PROJECT_TASK_FILE)

PROJECT_NEEDS_AUTODONE := $(if $(filter all,$(PROJECT)),,$(if $(filter $(PROJECT),$(PROJECT_QUEUE_LIST)),$(if $(strip $(FILTERED_TASKS)),$(if $(pending_queue),,$(if $(filter $(PROJECT),$(PROJECT_DONE_LIST)),,$(PROJECT))),),))
AUTO_DONE_LIST := $(if $(PROJECT_NEEDS_AUTODONE),$(strip $(PROJECT_DONE_LIST) $(PROJECT_NEEDS_AUTODONE)),$(PROJECT_DONE_LIST))
AUTO_PENDING_AFTER := $(if $(PROJECT_NEEDS_AUTODONE),$(filter-out $(AUTO_DONE_LIST),$(PROJECT_QUEUE_LIST)),$(PROJECT_PENDING_LIST))
AUTO_NEXT_DEFAULT := $(if $(PROJECT_NEEDS_AUTODONE),$(if $(AUTO_PENDING_AFTER),$(firstword $(AUTO_PENDING_AFTER)),$(firstword $(PROJECT_QUEUE_LIST))),$(PROJECT_DEFAULT))

ifneq ($(strip $(PROJECT_NEEDS_AUTODONE)),)
$(call write_project_state,$(AUTO_NEXT_DEFAULT),$(AUTO_DONE_LIST))
$(info [projects] auto-marked $(PROJECT_NEEDS_AUTODONE) as done; next=$(AUTO_NEXT_DEFAULT))
PROJECT_DONE_LIST := $(AUTO_DONE_LIST)
PROJECT_PENDING_LIST := $(AUTO_PENDING_AFTER)
PROJECT_DEFAULT := $(AUTO_NEXT_DEFAULT)
endif

ACTIVE_PROJECT := $(if $(filter all,$(PROJECT)),$(if $(PROJECT_PENDING_LIST),$(firstword $(PROJECT_PENDING_LIST)),$(firstword $(PROJECT_QUEUE_LIST))),$(PROJECT))
PROJECT_NEXT := $(if $(PROJECT_PENDING_LIST),$(firstword $(PROJECT_PENDING_LIST)),$(firstword $(PROJECT_QUEUE_LIST)))
TARGET_PROJECT := $(ACTIVE_PROJECT)
NEW_PROJECT_DONE := $(if $(filter $(TARGET_PROJECT),$(PROJECT_DONE_LIST)),$(PROJECT_DONE_LIST),$(strip $(PROJECT_DONE_LIST) $(TARGET_PROJECT)))
NEW_PROJECT_PENDING := $(filter-out $(NEW_PROJECT_DONE),$(PROJECT_QUEUE_LIST))
NEXT_AFTER_MARK := $(if $(NEW_PROJECT_PENDING),$(firstword $(NEW_PROJECT_PENDING)),$(firstword $(PROJECT_QUEUE_LIST)))

PROJECT_SUMMARY_DATE ?= $(shell date +%Y-%m-%d)
SUMMARY_HEADER := $(PROJECT_TITLE) Task Rollup ($(PROJECT_SUMMARY_DATE))
PENDING_HEADER := $(PROJECT_TITLE) Pending Tasks
DONE_HEADER := $(PROJECT_TITLE) Completed Tasks
STREAMS_HEADER := $(PROJECT_TITLE) Streams
NEXT_EMPTY_MESSAGE := All $(PROJECT_TITLE) tasks are complete.
STREAM_SUMMARY_HEADER = $(PROJECT_TITLE) Stream Summary ($(STREAMS))
STREAM_PENDING_HEADER = $(PROJECT_TITLE) Stream Pending ($(STREAMS))
STREAM_DONE_HEADER = $(PROJECT_TITLE) Stream Completed ($(STREAMS))
DEP_CHECK_HEADER := $(PROJECT_TITLE) Dependency Check
PROGRESS_HEADER := $(PROJECT_TITLE) Progress Metrics

CURRENT_PROJECT_NAME = $(call PROJECT_DISPLAY_NAME,$(PROJECT))
INDEX_OOB_MESSAGE = Requested index $(NEXT_INDEX) exceeds pending task count $(pending_total) for $(CURRENT_PROJECT_NAME).
INDEX_HINT_MESSAGE = Use make "next $(pending_total)" PROJECT=$(PROJECT) to view the last pending task.
PREV_OOB_MESSAGE = Requested index $(PREV_INDEX) exceeds completed task count $(completed_total) for $(CURRENT_PROJECT_NAME).
PREV_HINT_MESSAGE = Use make "prev $(completed_total)" PROJECT=$(PROJECT) to view the oldest completed task.
PREV_EMPTY_MESSAGE = No completed $(CURRENT_PROJECT_NAME) tasks found yet.

all: summary

summary: ## List all tasks with status indicators
	$(info $(SUMMARY_HEADER))
	$(foreach stream,$(STREAM_ORDER),$(call PRINT_STREAM,$(stream)))
	@:

todo: ## Show pending, in-progress, waiting, or optional tasks
	$(info $(PENDING_HEADER))
	$(foreach stream,$(STREAM_ORDER),$(call PRINT_STREAM_FILTER,$(stream),todo wip waiting blocked optional))
	@:

done: ## Show completed tasks
	$(info $(DONE_HEADER))
	$(foreach stream,$(STREAM_ORDER),$(call PRINT_STREAM_FILTER,$(stream),done))
	@:

list-proj: ## List project queue with status markers
	@echo "Project queue (default: $(PROJECT_DEFAULT))"
	@for p in $(PROJECT_QUEUE_LIST); do \
		status="pending"; \
		case " $(PROJECT_DONE_LIST) " in *" $$p "*) status="done";; esac; \
		marker=" "; \
		if [ "$$p" = "$(ACTIVE_PROJECT)" ]; then marker=">"; fi; \
		printf " %s %-12s [%s]\n" "$$marker" "$$p" "$$status"; \
	done

next-proj: ## Print the project currently queued for work
	@if [ -z "$(ACTIVE_PROJECT)" ]; then \
		echo "No active project computed."; \
	else \
		echo "$(ACTIVE_PROJECT)"; \
	fi

mark-proj-done: ## Mark the active project as complete and advance queue
	@if [ -z "$(TARGET_PROJECT)" ] || [ "$(TARGET_PROJECT)" = "all" ]; then \
		echo "Set PROJECT=<project> (one of $(PROJECT_QUEUE_LIST)) before marking done."; \
		exit 2; \
	fi
	@if ! printf '%s\n' $(PROJECT_QUEUE_LIST) | grep -qx "$(TARGET_PROJECT)"; then \
		echo "Unknown project $(TARGET_PROJECT). Use make list-proj to inspect the queue."; \
		exit 2; \
	fi
	@$(call write_project_state,$(NEXT_AFTER_MARK),$(NEW_PROJECT_DONE))
	@echo "[projects] marked $(TARGET_PROJECT) as done; next=$(NEXT_AFTER_MARK)"

progress: ## Show overall and per-stream completion metrics
	$(info $(PROGRESS_HEADER))
	$(if $(OVERALL_TOTAL),$(info Overall: done=$(OVERALL_DONE) / total=$(OVERALL_TOTAL) ($(call percent,$(OVERALL_DONE),$(OVERALL_TOTAL))) todo=$(OVERALL_TODO) wip=$(OVERALL_WIP) waiting=$(OVERALL_WAITING) blocked=$(OVERALL_BLOCKED) optional=$(OVERALL_OPTIONAL)),$(info No tasks registered for PROJECT=$(PROJECT).))
	$(foreach stream,$(STREAM_ORDER),$(call STREAM_PROGRESS_LINE,$(stream)))
	@:

mark: ## Update task STATUS/metadata (TASK=<ID> STATUS=todo|wip|done|blocked|waiting|optional)
	@$(if $(strip $(TASK)),,$(error TASK= required (e.g., make mark TASK=PMK_AUTO_MARK STATUS=wip)))
	@$(if $(strip $(STATUS)),,$(error STATUS= required (todo|wip|done|blocked|waiting|optional)))
	@$(PYTHON) "$(TASKS_CLI)" set --root "$(PROJECT_INCLUDE_DIR)" --task "$(TASK)" --status "$(STATUS)" \
	$(if $(OWNER), --owner "$(OWNER)",) \
	$(if $(DUE), --due "$(DUE)",) \
	$(if $(PRIO), --prio "$(PRIO)",) \
	$(if $(TAGS), --tags "$(TAGS)",) \
	$(if $(CRIT), --crit "$(CRIT)",) \
	$(if $(EVID), --evid "$(EVID)",)

mark.start mark.done mark.block mark.wait mark.optional: ## Convenience wrappers for common statuses
	@$(if $(strip $(TASK)),,$(error TASK= required (e.g., make mark.start TASK=PMK_AUTO_MARK)))
	@status=$(if $(filter mark.start,$@),wip,$(if $(filter mark.done,$@),done,$(if $(filter mark.block,$@),blocked,$(if $(filter mark.wait,$@),waiting,optional)))); \
	$(MAKE) --no-print-directory mark TASK="$(TASK)" STATUS="$$status" OWNER="$(OWNER)" DUE="$(DUE)" PRIO="$(PRIO)" TAGS="$(TAGS)" CRIT="$(CRIT)" EVID="$(EVID)"

next: ## Show the next recommended task and implementation prompt
ifeq ($(PROJECT),all)
	$(if $(strip $(PROJECT_QUEUE_LIST)),$(foreach proj,$(PROJECT_QUEUE_LIST),$(call PRINT_MULTI_NEXT,$(proj))),$(info No sub-projects registered.))
else
	$(call PRINT_SINGLE_NEXT)
endif
	@:

prev: ## Show the most recently completed task (pass index to step further back)
ifeq ($(PROJECT),all)
	$(if $(strip $(PROJECT_QUEUE_LIST)),$(foreach proj,$(PROJECT_QUEUE_LIST),$(call PRINT_MULTI_PREV,$(proj))),$(info No sub-projects registered.))
else
	$(call PRINT_SINGLE_PREV)
endif
	@:

streams: ## List stream labels
	$(info $(STREAMS_HEADER))
	$(foreach stream,$(STREAM_ORDER),$(info $(stream): $(call stream_label,$(stream))))
	@:

help: ## Print available targets
	$(info Available targets (set PROJECT=$(SUPPORTED_PROJECTS)):)
	$(info   make summary    - list $(PROJECT_TITLE) tasks with status markers)
	$(info   make todo       - show pending or optional tasks)
	$(info   make done       - show completed tasks)
	$(info   make progress   - show completion metrics and per-stream summary)
	$(info   make list-proj  - list the project queue with status markers)
	$(info   make next-proj  - show the project currently queued for work)
	$(info   make mark-proj-done - mark the active project complete and advance the queue)
	$(info   make mark       - update TASK status/metadata (TASK=ID STATUS=...))
	$(info   make mark.start - shortcut: STATUS=wip)
	$(info   make mark.done  - shortcut: STATUS=done)
	$(info   make mark.block - shortcut: STATUS=blocked)
	$(info   make mark.wait  - shortcut: STATUS=waiting)
	$(info   make mark.optional - shortcut: STATUS=optional)
	$(info   make next [n]   - show the nth actionable task with guidance)
	$(info   make prev [n]   - show the nth most recent completed task)
	$(info   make streams    - show stream key)
	$(info   make lint       - run dependency checks)
	$(info   pmake.summary   - shortcut for PROJECT=pmake summary)
	$(info   pmake.list-proj - shortcut for PROJECT=pmake list-proj)
	$(info   pmake.next-proj - shortcut for PROJECT=pmake next-proj)
	$(info   pmake.mark-proj-done - shortcut for PROJECT=pmake mark-proj-done)
	$(info   flex.summary    - shortcut for PROJECT=flexinvert summary)
	$(info   gsi.summary     - shortcut for PROJECT=gsi summary)
	$(info   wrf.summary     - shortcut for PROJECT=wrf summary)
	$(info   wrf-mcv.summary - shortcut for PROJECT=wrf-mcv summary)
	$(info   wrf-se.summary  - shortcut for PROJECT=wrf-se summary)
	@:

stream.summary: ## Show selected streams in STREAMS variable
	$(if $(strip $(STREAMS)),$(info $(STREAM_SUMMARY_HEADER))$(foreach stream,$(STREAMS),$(call PRINT_STREAM,$(stream))),$(error STREAMS not set for stream.summary))
	@:

stream.todo: ## Show pending tasks for selected streams (STREAMS="CATALOGUE AUTOMATION")
	$(if $(strip $(STREAMS)),$(info $(STREAM_PENDING_HEADER))$(foreach stream,$(STREAMS),$(call PRINT_STREAM_FILTER,$(stream),todo wip waiting blocked optional)),$(error STREAMS not set for stream.todo))
	@:

stream.done: ## Show completed tasks for selected streams (STREAMS="CATALOGUE AUTOMATION")
	$(if $(strip $(STREAMS)),$(info $(STREAM_DONE_HEADER))$(foreach stream,$(STREAMS),$(call PRINT_STREAM_FILTER,$(stream),done)),$(error STREAMS not set for stream.done))
	@:

lint: ## Validate dependency ordering (warn if prerequisites incomplete)
	$(info $(DEP_CHECK_HEADER))
	$(foreach task,$(DEP_TARGETS),$(call CHECK_TASK_DEPS,$(task)))
	@:

# --- Project shortcuts ----------------------------------------------------------

define PROJECT_SHORTCUTS
$(1).summary:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) summary
$(1).todo:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) todo
$(1).done:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) done
$(1).progress:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) progress
$(1).next:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) next
$(1).prev:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) prev
$(1).list-proj:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) list-proj
$(1).next-proj:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) next-proj
$(1).mark-proj-done:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) mark-proj-done
$(1).streams:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) streams
$(1).lint:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) lint
$(1).mark:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) mark TASK="$(TASK)" STATUS="$(STATUS)" OWNER="$(OWNER)" DUE="$(DUE)" PRIO="$(PRIO)" TAGS="$(TAGS)" CRIT="$(CRIT)" EVID="$(EVID)"
$(1).mark.start:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) mark.start TASK="$(TASK)" OWNER="$(OWNER)" DUE="$(DUE)" PRIO="$(PRIO)" TAGS="$(TAGS)" CRIT="$(CRIT)" EVID="$(EVID)"
$(1).mark.done:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) mark.done TASK="$(TASK)" OWNER="$(OWNER)" DUE="$(DUE)" PRIO="$(PRIO)" TAGS="$(TAGS)" CRIT="$(CRIT)" EVID="$(EVID)"
$(1).mark.block:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) mark.block TASK="$(TASK)" OWNER="$(OWNER)" DUE="$(DUE)" PRIO="$(PRIO)" TAGS="$(TAGS)" CRIT="$(CRIT)" EVID="$(EVID)"
$(1).mark.wait:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) mark.wait TASK="$(TASK)" OWNER="$(OWNER)" DUE="$(DUE)" PRIO="$(PRIO)" TAGS="$(TAGS)" CRIT="$(CRIT)" EVID="$(EVID)"
$(1).mark.optional:
	@$(MAKE) -s -f $(SELF_PATH) PROJECT=$(2) mark.optional TASK="$(TASK)" OWNER="$(OWNER)" DUE="$(DUE)" PRIO="$(PRIO)" TAGS="$(TAGS)" CRIT="$(CRIT)" EVID="$(EVID)"
endef

$(eval $(call PROJECT_SHORTCUTS,pmake,pmake))
$(eval $(call PROJECT_SHORTCUTS,flex,flexinvert))
$(eval $(call PROJECT_SHORTCUTS,gsi,gsi))
$(eval $(call PROJECT_SHORTCUTS,wrf,wrf))
$(eval $(call PROJECT_SHORTCUTS,wrf-mcv,wrf-mcv))
$(eval $(call PROJECT_SHORTCUTS,wrf-se,wrf-se))
